home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Commodore Free 31
/
Commodore_Free_Issue_31_2009_Commodore_Computer_Club.d64
/
c part 2.1
< prev
next >
Wrap
Text File
|
2023-02-26
|
16KB
|
617 lines
u
************************************
Alternative Programming Languages: C
Part 2
By Paul Davis
************************************
Welcome back to this brief look at
cross-development with the C
programming language. I hope you are
finding it interesting and are
inspired to learn more about this
useful language. As always, I welcome
your feedback and have now set up a
blog where you can leave comments on
any of my articles. The links for my
web site and blog can be found at the
end of this article.
Last time, I set a small challenge to
write a function that moves a sprite
to any position on the screen. How
did you do? Here is one possible
solution to that problem:
void move_sprite(int x, int y)
VIC.spr0_y = y;
if (x < 256)
VIC.spr_hi_x = 0;
VIC.spr0_x = x;
else
VIC.spr_hi_x = 1;
VIC.spr0_x = x - 256;
Bonus points are awarded if you came
up with something along these lines:
void move_sprite(int x, int y)
VIC.spr0_y = y;
VIC.spr0_x = x & 255;
VIC.spr_hi_x = x / 256;
Back to this issue and, as promised,
this time we are going to be creating
our very own sprite handling library.
In the process we will learn more
about how to build a C program from
several smaller components and how
these separate parts are compiled and
linked together. We will also
introduce some other tools to make
the development process a little
easier.
Getting started
We will be using a command line
environment to compile our programs,
so the first thing to do is start the
command prompt/terminal window
application for your particular
operating system. Next, we need to
make sure the environment is properly
configured. If you are running
Windows enter these commands:
cd c:\cc65\tut
init
The 'init.bat' script is available in
the zip file attachment for this
article on my web site. If you
previously set up the environment
permanently in System Properties you
do not need to run this script.
Linux and OSX users enter these
commands:
cd /Documents/cc65
. init.sh
Creating a header file
Before we jump in and create our
function library, let's take a step
back and see how to create and use a
header file. If you remember from
last time, header files are used to
declare the existence of various
functions, data types and constants
in the C libraries so we can use them
in our programs. There's nothing
particularly special about a header
file, it's just another plain text
file and we can easily create our
own.
Many of the programs we have seen so
far contain similar code at the
start. For example we have used the
'typedef' instruction to create a
type such as 'byte' to represent an
'unsigned char'. Rather than keep
repeating this code over and over in
every program we write, we can put
the code into a header file and just
include that file in our programs.
Let's try it. Edit a new file called
'mytypes.h' (OSX users replace
'notepad' in the command below with
'edit', Linux users replace it with
your favourite text editor):
notepad mytypes.h
Now enter (or copy and paste) the
following code into the file and save
it:
typedef unsigned char byte;
typedef unsigned int word;
typedef unsigned char bool;
#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif
As is becoming customary, I've
sneaked in some new language features
here. The first couple of typedef
lines should be familiar; they create
the new types for unsigned 8 and 16
bit values, byte and word
respectively. The third typedef is
for a Boolean (true or false) value.
The standard C language doesn't have
a dedicated Boolean type, it simply
treats any zero value as 'false' and
any non-zero value as 'true'. It's
often helpful to create such a type
to make the intent of our programs
clearer. For example, if a function
takes a 'bool' as a parameter, it's
more obvious that you should be
passing in a true or false value. If
that same function just declared its
parameter as a 'byte' it's not so
obvious. As you may have guessed, we
will be making use of this 'bool'
type in our sprite library.
The next group of lines create
constants for our new bool type, TRUE
and FALSE. But what's the deal with
the #ifndef part? This instruction is
one of several that C provides for
'conditional compilation'. The
instructions between that line and
the #endif line will only be compiled
if a certain condition is met. The
'#ifndef' instruction stands for 'if
not defined'. In other words the
whole line means 'compile the
following lines only if TRUE has not
already been defined'. Why would it
already be defined, you ask? It turns
out that making a 'bool' type and the
TRUE/FALSE constants is a fairly
common practice. It's possible that
other header files from other
libraries will have such definitions.
Since C doesn't allow constants to be
re-defined, we use #ifndef as a
precaution so that our header file
doesn't try to define the values
again if they already exist.
To test our header file we need to
create a program that uses it. Edit a
new file called 'bool.c' and enter
this program into it:
#include <stdio.h>
#include "mytypes.h"
void main(void)
byte x = 123;
word y = 12345;
bool z = TRUE;
printf("x = %d\n", x);
printf("y = %d\n", y);
printf("z = %d\n", z);
printf("z is %s\n",
z ? "true":"false");
Compile the program using the
command:
cl65 bool.c
Running the program in the VICE
emulator will print out the numeric
values of the variables along with
the true/false value of the 'z'
variable in words.
This program includes our header file
to allow it to use the new types.
Notice how, in the #include line, the
file name is contained in double
quotes rather than 'angled brackets'.
This is important. The brackets tell
C to search for the file in the
compiler's 'include' directory.
Double quotes tell C to search for
the file in the current working
directory.
Another new language feature has
crept in here. On the final printf
line, we use what is called the
'tertiary if operator' represented by
the question mark. This is a very
useful mechanism in C for performing
an 'if' without the full-blown syntax
of the 'if' statement. It has this
form:
condition ? true_value :
false_value
The part before the question mark is
a condition, something that has a
true or false result. In this
example, the variable 'z' has such a
value already so the variable itself
is the condition. After the question
mark is the value to use if the
condition was true, in this case we
use the literal string "true". The
value to use when the condition is
false is separated from the true
value by a colon. Here we just use
the literal string "false". The end
result of all this is that when the
printf function is called, the value
of the z variable is tested and if
its value is true, the string "true"
is passed as a parameter, otherwise
the string "false" is passed instead.
This is a much neater solution than
the alternatives using a standard
'if' statement, such as:
if (z) printf("z is true\n");
else printf("z is false\n");
or:
char *zstr;
if (z) zstr = "true";
else zstr = "false";
printf("z is %s\n", zstr);
Learning how to write and use a
header file is the first step towards
creating our function library. Next,
we need a little more understanding
of the how the compiler translates
our C source code into an executable.
The compilation process
Up until now, we've been using the
'cl65' command to compile our
programs. This is actually a
convenience command provided by the
cc65 authors to make compiling simple
programs as easy as possible. But now
it's time to learn the individual
steps in the compilation process.
To start with, let's create an
'empty' program. Create a new file
called 'sprtst.c' and enter the
following code into it:
void main(void)
Remember from the first article that
a C program must have a main
function, so this represents the
simplest possible 'program' for
testing purposes, one that does
absolutely nothing.
Now, we're not going to compile this
using the familiar cl65 command.
Instead we're going to go through the
same steps by hand that cl65 would
automatically do for us. First, we
need to compile the program source
code into assembly language using the
cc65 command as follows:
cc65 -t c64 sprtst.c
One thing to note here is that we now
have to specify the target machine
using the '-t c64' option. The cc65
compiler works with many 6502 based
systems and needs to be told which
one we want to use. The cl65 command
automatically assumes c64 by default,
but the individual compilation
commands do not.
Once the program has compiled,
Windows users enter this command:
dir sprtst.*
OSX and Linux users enter this
command:
ls -l sprtst*
This command lists all files called
'sprtst' with different extensions.
You should see our 'sprtst.c' source
file and a new file created by the
cc65 command called 'sprtst.s'. This
is the result of the C code being
translated to assembly language.
The next step is to assemble this
file into machine code using the ca65
assembler. Enter the following
command:
ca65 -t c64 sprtst.s
Enter the dir (or ls) command from
before and you will see a new file
'sprtst.o' in the list. Here's a tip
for anyone who is not familiar with
using a command line environment. You
can repeat previously typed commands
by pressing the cursor up key until
the command you want to run is shown
then pressing the return key to run
it.
The output from the assembler is not
a complete program that you can run
on the Commodore. Files with a '.o'
extension are called 'object files'
and they contain, among other things,
machine code fragments. Why create
this intermediate file? Well, it
allows a C program to be built up
from multiple components. You have
already seen that many C functions
are stored in libraries. These
libraries need to be linked together
with our own code to create the
complete executable program. The tool
used to perform this linking is,
unsurprisingly, called a linker and
is run as follows (enter this all on
one line):
ld65 -t c64 -o sprtst c64.o
sprtst.o c64.lib
This command is more complex than the
previous ones and requires some
explanation. The '-t' option as usual
indicates we are creating a program
for the c64. The '-o' option tells
the linker the name of the output
file, that is the filename of the
finished executable program. Here we
are calling it 'sprtst'. Following
these options are the names of all
the files that are linked together to
create our program. You will
recognise 'sprtst.o' as our program,
but what are the other files?
As mentioned previously, every
program starts at the 'main'
function. This doesn't happen by
magic, there is actually a small
piece of 'startup' code that is
placed at the beginning of every
program that performs some
initialisation such as clearing
memory and then calls the 'main'
function. This 'startup code' is
contained within the file called
'c64.o' and must be the first file
listed in the link process. The last
file, c64.lib contains those standard
library functions we have been using
all this time. The code for any
functions called by our program will
be copied from this file and linked
into our program.
Run the dir (or ls) command again and
you should now see 4 files,
'sprtst.c', 'sprtst.s', 'sprtst.o'
and finally the executable file
'sprtst'. You could run this program
if you like but, as you already know,
it doesn't do anything? yet!
Okay, I can imagine you're thinking
that's a lot of effort to compile a
program that does nothing! Indeed it
is, and the cl65 command was provided
to remove the need for this effort.
But, bear in mind the goal of this
article is to create a function
library. As you may have already
surmised from what we have just done,
to create a library we need to be
able to create 'object files', and to
do that we need to use the separate
compilation commands.
No doubt the thought of repeatedly
typing all those separate commands
isn't appealing. Fear not, we are now
going to take a look at a tool that
can alleviate some of the pain. It's
called 'make'.
Makefiles
Before I can show you how 'make'
works it needs to be installed on
your computer. OSX and Linux users
are lucky enough to already have it
but Windows users will need to
install a copy. Since the other
operating systems use the gnu
implementation of 'make' we will, for
consistency, also use this on
Windows.
The gnu tools are hosted on the
sourceforge web site but I'm going to
take the opportunity to introduce
another useful package called
'unixutils'. This package contains
many gnu unix tools that have been
compiled natively for Windows, that
is they don't require any other DLLs
or 'unix emulation libraries' to be
installed in order to use them. They
are older versions of the tools but
work well and are easy to install. If
you are more familiar with the gnu
tools and unix libraries for Windows,
feel free to install whatever version
of 'make' you like.
Unixutils can be found at
http://sourceforge.net/projects/unxuti
ls. Click on the big green download
link, then on the download link of
the latest entry in the list of files
that appears. Finally, click on
UnxUtils.zip to download the file.
Next, unzip this file to a temporary
directory and navigate to the
usr\local\wbin directory. In here
locate the 'make.exe' file and copy
it to your c:\cc65\bin directory.
This isn't particularly good
practice, but it's the simplest way
of getting up and running since our
environment is already set up to look
for commands in the cc65\bin
directory. If you are more
experienced with such things, you can
install the entire utils package to a
directory of your choice and add the
'bin' directory to the 'Path'
environment variable.
Now, to test that we can run the
command, enter this line:
make
You should get an error message from
'make' saying it can't find a target
or a makefile. If you get a message
saying that 'make' is not recognised
as a command, ensure that you have
placed the make.exe file in the
c:\cc65\bin directory and try again.
Right, OSX and Linux users can join
us again now. So how can 'make' help
us to build our programs? The best
way to explain is by example. We need
to create a new file called
'makefile' (with no extension). OSX
and Linux users can just create the
file in the usual way, but if you are
using Notepad on Windows there is a
problem. If you entered the command
'notepad makefile' it would actually
create a new file called
'makefile.txt'. To get around this,
enter the command with a trailing
'dot':
notepad makefile.
Now enter the following lines into
the makefile. Be very careful here,
the lines containing the commands
(ld65, ca65, cc65) must be indented
using the tab key, not spaces.
Unfortunately, this means cutting and
pasting this text in its entirety
will not work. You can copy a line at
a time, just remember to press tab
before each command.
sprtst: sprtst.o
ld65 -t c64 -o sprtst c64.o
sprtst.o c64.lib
sprtst.o: sprtst.s
ca65 -t c64 sprtst.s
sprtst.s: sprtst.c
cc65 -t c64 sprtst.c
You will recognise these commands as
the ones we entered a short while
ago. So let's explain how this file
works.
An entry in a makefile is formatted
like this:
target-file: dependent-files
commands to build target-file
A line that starts with a filename
followed by a colon indicates a
'target' file, that is, a file you
want to create from some other files.
Those other files are listed after
the colon and are known as the
dependents of the target file.
Following this line is one or more
command lines, each indented by a tab
character. When run, these commands
will create the target file from its
dependents.
Looking at the makefile we have just
created, we can see that the 'sprtst'
file is dependent on the 'sprtst.o'
object file and uses the ld65 command
to build the program from this file.
The 'sprtst.o' file is, in turn,
dependent on the 'sprtst.s' file and
is created from it with the ca65
command. Finally, the 'sprtst.s' file
is dependent on the 'sprtst.c' file
and is created from it using the cc65
command.
As you can see, we have built a
'chain' of dependencies and
corresponding actions. This is
extremely useful because it allows
'make' to work out what commands need
to be run whenever changes have been
made to a particular file. Edit the
'sprtst.c' file and add an include
line to the start of it:
#include "mytypes.h"
void main(void)
CONTINUED IN PART 2.2